package simpleTaskDispatcher.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import junit.framework.Assert;
import junit.framework.TestCase;

import org.junit.Test;

import simpleTaskDispatcher.model.enums.Priority;
import simpleTaskDispatcher.model.enums.TaskPlacement;
import simpleTaskDispatcher.model.taskDispatchers.TaskQueueComparators;
import simpleTaskDispatcher.model.taskDispatchers.impl.Dispatchers;
import simpleTaskDispatcher.model.tasks.ITask;
import simpleTaskDispatcher.model.tasks.impl.AbstractTask;
import simpleTaskDispatcher.model.tasks.impl.ContextFreeTask;

public class TestDispatcher {

	
	/**
	 * 
	 * @throws InterruptedException
	 */
	@Test
	public void testFocusOnOldestTaskQueue() throws InterruptedException {
		
		BlockingQueue<String> eventsLogs = new LinkedBlockingQueue<String>();
		
		WorkersPool p = new WorkersPool();
		p.setDispatcher(Dispatchers.focusOnFirstsTaskQueue(2, TaskQueueComparators.compareByAscCreation()));
		p.createWorkers(4);
		TaskQueue taskQueue1 = new TaskQueue("TaskQueue1", 1, 3);
		TaskQueue taskQueue2 = new TaskQueue("TaskQueue2", 1, 3);
		
		assertCurrentMinOptimalMax(0, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(0, 1, 3, 3, taskQueue2);
		
		p.addTaskQueue(taskQueue1);		
		p.addTaskQueue(taskQueue2);

		TestTask t1_1 = new TestTask("1_1", eventsLogs);
		taskQueue1.put(t1_1);
		TestTask t1_2 = new TestTask("1_2", eventsLogs);
		taskQueue1.put(t1_2);
		TestTask t1_3 = new TestTask("1_3", eventsLogs);
		taskQueue1.put(t1_3);
		TestTask t1_4 = new TestTask("1_4", eventsLogs);
		taskQueue1.put(t1_4);
		TestTask t1_5 = new TestTask("1_5", eventsLogs);
		taskQueue1.put(t1_5);
		TestTask t1_6 = new TestTask("1_6", eventsLogs);
		TestTask t1_7 = new TestTask("1_7", eventsLogs);
		
		TestTask t2_1 = new TestTask("2_1", eventsLogs);
		taskQueue2.put(t2_1);
		TestTask t2_2 = new TestTask("2_2", eventsLogs);
		taskQueue2.put(t2_2);
		TestTask t2_3 = new TestTask("2_3", eventsLogs);
		taskQueue2.put(t2_3);
		TestTask t2_4 = new TestTask("2_4", eventsLogs);
		taskQueue2.put(t2_4);
		TestTask t2_5 = new TestTask("2_5", eventsLogs);
		TestTask t2_6 = new TestTask("2_6", eventsLogs);

		assertInLogs(eventsLogs, "start of 1_1", "start of 1_2", "start of 1_3", "start of 2_1");
		assertCurrentMinOptimalMax(3, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(1, 1, 3, 3, taskQueue2);
		
		t1_1.finished();
		t2_1.finished();
		
		assertInLogs(eventsLogs, "end of 1_1", "end of 2_1", "start of 1_4", "start of 2_2");

		taskQueue1.setMaximalWorkers(2, TaskPlacement.PLACE_AT_END_OF_QUEUE);
		
		Thread.sleep(100);
		assertCurrentMinOptimalMax(3, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(1, 1, 3, 3, taskQueue2);
		
		t1_4.finished();
		
		assertInLogs(eventsLogs, "end of 1_4", "start of 1_5");
		assertCurrentMinOptimalMax(3, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(1, 1, 3, 3, taskQueue2);
		
		t1_5.finished();
		
		assertInLogs(eventsLogs, "end of 1_5", "start of 2_3");
		assertCurrentMinOptimalMax(2, 1, 2, 2, taskQueue1);
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue2);
		
		t1_3.finished();
		assertInLogs(eventsLogs, "end of 1_3");
		assertCurrentMinOptimalMax(2, 1, 2, 2, taskQueue1);
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue2);
		
		taskQueue1.setMaximalWorkers(1, TaskPlacement.PLACE_AT_END_OF_QUEUE);
		
		assertInLogs(eventsLogs, "start of 2_4");
		assertCurrentMinOptimalMax(1, 1, 1, 1, taskQueue1);
		assertCurrentMinOptimalMax(3, 1, 3, 3, taskQueue2);
		
		taskQueue1.setMaximalWorkers(2, TaskPlacement.PLACE_AT_END_OF_QUEUE);
		t2_4.finished();
		
		assertInLogs(eventsLogs, "end of 2_4");
	
		taskQueue1.put(t1_6);
		taskQueue1.put(t1_7);
		taskQueue2.put(t2_5);
		taskQueue2.put(t2_6);
		assertInLogs(eventsLogs, "start of 2_5");
		
		taskQueue2.setMaximalWorkers(2, TaskPlacement.PLACE_AT_START_OF_QUEUE);
		t2_5.finished();
		assertInLogs(eventsLogs, "end of 2_5");
		
		
		t1_2.finished();
		//The setMaximalWorker(2) is executed here
		assertInLogs(eventsLogs, "end of 1_2", "start of 1_6", "start of 1_7");
	}
	

	@Test
	public void testGiveEqualResources() throws InterruptedException {
		
		WTFThread wtft = new WTFThread();
		wtft.start();
		
		BlockingQueue<String> eventsLogs = new LinkedBlockingQueue<String>();
		
		WorkersPool p = new WorkersPool();
		p.setDispatcher(Dispatchers.giveEqualResource(2, TaskQueueComparators.compareByAscCreation()));
		p.createWorkers(4);
		TaskQueue taskQueue1 = new TaskQueue("TaskQueue1", 1, 3);
		TaskQueue taskQueue2 = new TaskQueue("TaskQueue2", 1, 3);
		
		assertCurrentMinOptimalMax(0, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(0, 1, 3, 3, taskQueue2);
		
		p.addTaskQueue(taskQueue1);		
		p.addTaskQueue(taskQueue2);

		TestTask t1_1 = new TestTask("1_1", eventsLogs);
		taskQueue1.put(t1_1);
		TestTask t1_2 = new TestTask("1_2", eventsLogs);
		taskQueue1.put(t1_2);
		TestTask t1_3 = new TestTask("1_3", eventsLogs);
		taskQueue1.put(t1_3);
		TestTask t1_4 = new TestTask("1_4", eventsLogs);
		taskQueue1.put(t1_4);
		TestTask t1_5 = new TestTask("1_5", eventsLogs);
		taskQueue1.put(t1_5);
		TestTask t1_6 = new TestTask("1_6", eventsLogs);
		taskQueue1.put(t1_6);
		TestTask t1_7 = new TestTask("1_7", eventsLogs);
		taskQueue1.put(t1_7);
		TestTask t1_8 = new TestTask("1_8", eventsLogs);
		taskQueue1.put(t1_8);
		TestTask t1_9 = new TestTask("1_9", eventsLogs);
		taskQueue1.put(t1_9);

		TestTask t2_1 = new TestTask("2_1", eventsLogs);
		taskQueue2.put(t2_1);
		TestTask t2_2 = new TestTask("2_2", eventsLogs);
		taskQueue2.put(t2_2);
		TestTask t2_3 = new TestTask("2_3", eventsLogs);
		taskQueue2.put(t2_3);
		TestTask t2_4 = new TestTask("2_4", eventsLogs);
		taskQueue2.put(t2_4);
		TestTask t2_5 = new TestTask("2_5", eventsLogs);
		taskQueue2.put(t2_5);
		TestTask t2_6 = new TestTask("2_6", eventsLogs);
		taskQueue2.put(t2_6);
		TestTask t2_7 = new TestTask("2_7", eventsLogs);
		taskQueue2.put(t2_7);
		TestTask t2_8 = new TestTask("2_8", eventsLogs);
		taskQueue2.put(t2_8);
		TestTask t2_9 = new TestTask("2_9", eventsLogs);
		taskQueue2.put(t2_9);
		

		assertInLogs(eventsLogs, "start of 1_1", "start of 1_2", "start of 2_1", "start of 2_2");
		
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue2);
		
		taskQueue1.setMaximalWorkers(1, TaskPlacement.PLACE_AT_START_OF_QUEUE);
		
		t1_1.finished();
		t2_1.finished();
		t1_2.finished();
		t2_2.finished();
		
		assertInLogs(eventsLogs, "end of 1_1", "end of 2_1", "end of 1_2", "end of 2_2", "start of 1_3", "start of 2_3", "start of 2_4", "start of 2_5");
		assertCurrentMinOptimalMax(1, 1, 1, 1, taskQueue1);
		assertCurrentMinOptimalMax(3, 1, 3, 3, taskQueue2);
		
		taskQueue1.setMaximalWorkers(3, TaskPlacement.PLACE_AT_START_OF_QUEUE);
		
		assertCurrentMinOptimalMax(1, 1, 1, 1, taskQueue1);
		assertCurrentMinOptimalMax(3, 1, 3, 3, taskQueue2);
		
		t1_3.finished();
		Thread.sleep(1000);
		
		assertCurrentMinOptimalMax(1, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue2);

		p.doDispatching();
		
		t2_3.finished();		
		t2_4.finished();
		t2_5.finished();
		
		assertInLogs(eventsLogs, "end of 1_3", "end of 2_3", "end of 2_4", "end of 2_5", "start of 1_4", "start of 1_5", "start of 2_6", "start of 2_7");
		
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue2);
		
	}
	
	@Test
	public void testPutQueuesOnStandby() throws InterruptedException {
		
		WTFThread wtft = new WTFThread();
		wtft.start();
		
		BlockingQueue<String> eventsLogs = new LinkedBlockingQueue<String>();
		
		WorkersPool p = new WorkersPool();
		p.setDispatcher(Dispatchers.giveEqualResource(2, TaskQueueComparators.compareByPriority(false)));
		p.createWorkers(4);
		TaskQueue taskQueue1 = new TaskQueue("TaskQueue1", 1, 3).setPriority(Priority.MIDDLE);
		TaskQueue taskQueue2 = new TaskQueue("TaskQueue2", 1, 3).setPriority(Priority.LOW);
		TaskQueue taskQueue3 = new TaskQueue("TaskQueue3", 1, 3).setPriority(Priority.HIGH);
		
		assertCurrentMinOptimalMax(0, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(0, 1, 3, 3, taskQueue2);
		
		p.addTaskQueue(taskQueue1);		
		p.addTaskQueue(taskQueue2);

		TestTask t1_1 = new TestTask("1_1", eventsLogs);
		taskQueue1.put(t1_1);
		TestTask t1_2 = new TestTask("1_2", eventsLogs);
		taskQueue1.put(t1_2);
		TestTask t1_3 = new TestTask("1_3", eventsLogs);
		taskQueue1.put(t1_3);
		TestTask t1_4 = new TestTask("1_4", eventsLogs);
		taskQueue1.put(t1_4);
		TestTask t1_5 = new TestTask("1_5", eventsLogs);
		taskQueue1.put(t1_5);
		TestTask t1_6 = new TestTask("1_6", eventsLogs);
		taskQueue1.put(t1_6);

		TestTask t2_1 = new TestTask("2_1", eventsLogs);
		taskQueue2.put(t2_1);
		TestTask t2_2 = new TestTask("2_2", eventsLogs);
		taskQueue2.put(t2_2);
		TestTask t2_3 = new TestTask("2_3", eventsLogs);
		taskQueue2.put(t2_3);
		TestTask t2_4 = new TestTask("2_4", eventsLogs);
		taskQueue2.put(t2_4);
		TestTask t2_5 = new TestTask("2_5", eventsLogs);
		taskQueue2.put(t2_5);
		TestTask t2_6 = new TestTask("2_6", eventsLogs);
		taskQueue2.put(t2_6);
		
		TestTask t3_1 = new TestTask("3_1", eventsLogs);
		taskQueue3.put(t3_1);
		TestTask t3_2 = new TestTask("3_2", eventsLogs);
		taskQueue3.put(t3_2);
		TestTask t3_3 = new TestTask("3_3", eventsLogs);
		taskQueue3.put(t3_3);
		TestTask t3_4 = new TestTask("3_4", eventsLogs);
		taskQueue3.put(t3_4);
		TestTask t3_5 = new TestTask("3_5", eventsLogs);
		taskQueue3.put(t3_5);
		TestTask t3_6 = new TestTask("3_6", eventsLogs);
		taskQueue3.put(t3_6);
		
		assertInLogs(eventsLogs, "start of 1_1", "start of 2_1", "start of 1_2", "start of 2_2");
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue2);
		
		p.addTaskQueue(taskQueue3);
		
		t1_1.finished();
		t1_2.finished();
		t2_1.finished();
		t2_2.finished();
		
		assertInLogs(eventsLogs, "end of 1_1", "end of 2_1", "end of 1_2", "end of 2_2", "start of 1_3", "start of 1_4", "start of 3_1", "start of 3_2");
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(0, 1, 3, 3, taskQueue2);
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue3);
		
	}
	
	@Test
	public void testContinueStartedTaskQueues() throws InterruptedException {
		
		WTFThread wtft = new WTFThread();
		wtft.start();
		
		BlockingQueue<String> eventsLogs = new LinkedBlockingQueue<String>();
		
		WorkersPool p = new WorkersPool();
		p.setDispatcher(Dispatchers.giveEqualResource(2, TaskQueueComparators.compareByPriority(true)));
		p.createWorkers(4);
		TaskQueue taskQueue1 = new TaskQueue("TaskQueue1", 1, 3).setPriority(Priority.MIDDLE);
		TaskQueue taskQueue2 = new TaskQueue("TaskQueue2", 1, 3).setPriority(Priority.LOW);
		TaskQueue taskQueue3 = new TaskQueue("TaskQueue3", 1, 3).setPriority(Priority.HIGH);
		
		assertCurrentMinOptimalMax(0, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(0, 1, 3, 3, taskQueue2);
		
		p.addTaskQueue(taskQueue1);		
		p.addTaskQueue(taskQueue2);

		TestTask t1_1 = new TestTask("1_1", eventsLogs);
		taskQueue1.put(t1_1);
		TestTask t1_2 = new TestTask("1_2", eventsLogs);
		taskQueue1.put(t1_2);
		TestTask t1_3 = new TestTask("1_3", eventsLogs);
		taskQueue1.put(t1_3);
		TestTask t1_4 = new TestTask("1_4", eventsLogs);
		taskQueue1.put(t1_4);
		TestTask t1_5 = new TestTask("1_5", eventsLogs);
		taskQueue1.put(t1_5);
		TestTask t1_6 = new TestTask("1_6", eventsLogs);
		taskQueue1.put(t1_6);

		TestTask t2_1 = new TestTask("2_1", eventsLogs);
		taskQueue2.put(t2_1);
		TestTask t2_2 = new TestTask("2_2", eventsLogs);
		taskQueue2.put(t2_2);
		TestTask t2_3 = new TestTask("2_3", eventsLogs);
		taskQueue2.put(t2_3);
		TestTask t2_4 = new TestTask("2_4", eventsLogs);
		taskQueue2.put(t2_4);
		TestTask t2_5 = new TestTask("2_5", eventsLogs);
		taskQueue2.put(t2_5);
		TestTask t2_6 = new TestTask("2_6", eventsLogs);
		taskQueue2.put(t2_6);
		
		TestTask t3_1 = new TestTask("3_1", eventsLogs);
		taskQueue3.put(t3_1);
		TestTask t3_2 = new TestTask("3_2", eventsLogs);
		taskQueue3.put(t3_2);
		TestTask t3_3 = new TestTask("3_3", eventsLogs);
		taskQueue3.put(t3_3);
		TestTask t3_4 = new TestTask("3_4", eventsLogs);
		taskQueue3.put(t3_4);
		TestTask t3_5 = new TestTask("3_5", eventsLogs);
		taskQueue3.put(t3_5);
		TestTask t3_6 = new TestTask("3_6", eventsLogs);
		taskQueue3.put(t3_6);
		
		assertInLogs(eventsLogs, "start of 1_1", "start of 2_1", "start of 1_2", "start of 2_2");
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue2);
		
		p.addTaskQueue(taskQueue3);
		
		t1_1.finished();
		t1_2.finished();
		t2_1.finished();
		t2_2.finished();
		
		assertInLogs(eventsLogs, "end of 1_1", "end of 2_1", "end of 1_2", "end of 2_2", "start of 1_3", "start of 1_4", "start of 2_3", "start of 2_4");
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue1);
		assertCurrentMinOptimalMax(2, 1, 3, 3, taskQueue2);
		assertCurrentMinOptimalMax(0, 1, 3, 3, taskQueue3);
		
	}
	
	private void assertInLogs(BlockingQueue<String> eventsLogs, String... logEvents) throws InterruptedException {
		while (eventsLogs.size() < logEvents.length){Thread.sleep(100);}
		Thread.sleep(100);
		
		List<String> expectedEvents = new ArrayList<String>();
		for(String logEvent : logEvents) {
			expectedEvents.add(logEvent);
		}
		
		List<String> events = new ArrayList<String>();
		String event = null;
		while((event = eventsLogs.poll()) != null) {
			events.add(event);
		}
		
		Collections.sort(expectedEvents);
		Collections.sort(events);
		
		TestCase.assertEquals(expectedEvents, events);
		
	}
	
	private void assertCurrentMinOptimalMax(int current, int min, int optimal, int max, TaskQueue taskQueue) {
		TestCase.assertEquals(current, taskQueue.getPlannedNbWorkers());
		TestCase.assertEquals(min, taskQueue.getMinimalWorkers());
		TestCase.assertEquals(max, taskQueue.getMaximalWorkers());
		TestCase.assertEquals(optimal, taskQueue.getOptimalWorkers());
	}
	
}
